with Legend.Roles;
with SDL.Events, SDL.Keysym;
with Ada.Containers.Doubly_Linked_Lists, Ada.Containers.Ordered_Sets;
with Legend.Interactives;
with Legend.Menus;
with Legend.Controls;
private with Performances;
package Legend.Stages is

   -- There are 3 kinds stages : world, room, warfield
   -- world compose with earth, surface, building, bx, by;
   -- room compose with earth, building, hung, story, hung_x, hung_y;
   -- warfield compose with earth, building.
   -- Buildings should sort before paint.
   -- Hung item should caculate canvas position with hung_x, hung_y
   -- Story point to all_scene_stories(scene_id)(story_id), which contains
   -- movie_frame if story is visible.

   -- 西    ◆     北
   --     ◆◆◆
   --   ◆◆◆◆◆
   -- ◆◆◆◆◆◆◆
   --   ◆◆◆◆◆
   --     ◆◆◆
   -- 南    ◆     东

   -- 地图坐标系 (
   --	 西北为原点, x轴为西东方向, y轴为北南方向
   -- )
   -- 视觉坐标系 (
   --	 左上为原点, x轴为左右方向, y轴为上下方向, 
   --	 逻辑屏幕, 
   --	 一个地图格点需要用2x2个视觉格点表示
   -- )
   -- 画布坐标系 (
   --	 左上为原点, x轴为左右方向, y轴为上下方向, 
   --	 物理屏幕, 
   --	 一个视觉格点在画布上的大小为: 
   --	    (Width => Grid_Size.x, Height => Grid_Size.y)
   -- )



   -- 场景
   type Stage_Type is new Interactives.Interactive_Type with private;

   -- 场景虚类
   subtype Stage_Class is Stage_Type'Class;

   -- 场景虚指针
   type Stage_Pointer is access all Stage_Type'Class;

   -- 初始化
   procedure Setup (
      this      : in out Stage_Type;
      Map_Size  : Point_Type;
      Grid_Size : Point_Type := (x => 18, y => 9);
      Pictures  : Pictures_Pointer;
      Scene     : access Scene_Type := null
      );

   function Get_Map_Region (
      this : Stage_Type
      ) return Region_Type;

   function Get_Pictures (
      this : in out Stage_Type
      ) return Pictures_Pointer;

   -- 重绘画布
   overriding
   procedure Paint (this : in out Stage_Type);

   -- 重绘画布指定区域
   procedure Paint (
      this   : in out Stage_Type;
      Region : Region_Type
      );

   -- 碰撞检测
   -- 本函数不对m作边界检测, 因此如有必要请在调用前进行边界检测
   function Has_Obstacle_At (
      this  : Stage_Type;
      M     : Point_Type;
      Horse : Horse_Type
      ) return Boolean;

   procedure Get_Horse_At (
      this  : Stage_Type;
      M     : Point_Type;
      Horse : in out Horse_Type
      ) is null;

   procedure Set_Horse_At (
      this  : in out Stage_Type;
      M     : Point_Type;
      Horse : Horse_Type;
      Face  : Face_Type
      ) is null;

   -- 设置指定地图位置建筑物贴图编号
   procedure Set_Building_At (
      this : in out Stage_Type;
      M    : Point_Type;
      B    : Uint16_t
      );

   -- 根据建筑区域的任一点获取建筑物的东南角坐标
   function Get_Building_Location (
      this : Stage_Type;
      m : Point_Type
      ) return Point_Type;

   function Stories (
      this : Stage_Type
      ) return Stories_Pointer;

   function Current_Story (
      this : in out Stage_Type
      ) return access Story_Type;

   function Get_Scene_Id (
      this : Stage_Type
      ) return Uint16_t;

   function Get_Story_Id (
      this : Stage_Type
      ) return Uint16_t;

   procedure Check_Story (
      this  : in out Stage_Type;
      Where : Point_Type;
      Cause : Script_Cause_Type
      );


   procedure Set (
      this  : in out Stage_Type;
      Layer : Map_Layer_Type;
      Where : Point_Type;
      Value : Uint16_t
      );

   function Get (
      this  : Stage_Type;
      Layer : Map_Layer_Type;
      Where : Point_Type
      ) return Uint16_t;

   procedure Replace_Map (
      this  : in out Stage_Type;
      Layer : Map_Layer_Type;
      From  : Uint16_t;
      To    : Uint16_t
      );

      

   -- 转换地图坐标到视觉坐标
   procedure Map_To_View (
      this : Stage_Type;
      M    : Point_Type;
      V    : out Point_Type
      );

   -- 转换视觉坐标到地图坐标
   procedure View_To_Map (
      this : Stage_Type;
      V    : Point_Type;
      M    : out Point_Type
      );

   -- 转换视觉坐标到画布坐标
   procedure View_To_Canvas (
      this : Stage_Type;
      V    : Point_Type;
      C    : out Point_Type
      );

   -- 转换视觉区域到地图区域
   procedure View_Region_To_Map_Region (
      this : Stage_Type;
      V    : Region_Type;
      M    : out View_Corners_Type
      );

   -- 转换地图区域到视觉区域
   procedure Map_Region_To_View_Region (
      this : Stage_Type;
      M    : Region_Type;
      V    : out Region_Type
      );

   -- 检查指定位置是否在地图范围之内
   function Is_In_Map (
      this : Stage_Type;
      M    : Point_Type
      ) return Boolean;

   -- 检查指定地图位置是否在可见区域中
   function Is_In_Sight (
      this : Stage_Type;
      M    : Point_Type
      ) return Boolean;

   -- 获取画布中心的地图坐标
   function Get_Canvas_Center_As_Map (
      this : Stage_Type
      ) return Point_Type;

   -- 滚动视觉地图
   procedure Scroll_View_By (
      this   : in out Stage_Type;
      Offset : Point_Type
      );

   -- 滚动地图
   procedure Scroll_Map_By (
      this   : in out Stage_Type;
      Offset : Point_Type
      );

   -- 滚动地图使指定地图位置成为画布中心点
   procedure Scroll_To_Canvas_Center (
      this : in out Stage_Type;
      M    : Point_Type
      );

   -- 检查指定位置是否当前场景入口
   function Has_Door_At (
      this : in out Stage_Type;
      M    : Point_Type
      ) return Boolean;

   -- 检查指定位置是否当前场景出口
   function Has_Exit_At (
      this : in out Stage_Type;
      M    : Point_Type
      ) return Boolean;

   procedure Set_Movie_At (
      this    : in out Stage_Type;
      Where   : Point_Type;
      Movie   : Movie_Type;
      Layer   : Map_Layer_Type;
      Expired : Duration := Loop_Infinite;
      Replace : Boolean := True
      ) with Pre => Layer in ml_Earth | ml_Surface | ml_Building;

   procedure Move_Movie (
      this  : in out Stage_Type;
      Layer : Map_Layer_Type;
      From  : Point_Type;
      To    : Point_Type
      ) with Pre => Layer in ml_Earth | ml_Surface | ml_Building;

   procedure Remove_Movie_At (
      this  : in out Stage_Type;
      Layer : Map_Layer_Type;
      Where : Point_Type
      ) with Pre => Layer in ml_Earth | ml_Surface | ml_Building;

   -- 查找场景
   function Find_Scene_At (
      this  : in out Stage_Type;
      Door  : Point_Type;
      Scene : out Uint16_t
      ) return Boolean;

   -- 进入子场景
   procedure Switch_To_Room (
      this  : in out Stage_Type;
      Scene : access Scene_Type
      );

   -- 进入战斗场景
   procedure Switch_To_War (
      this : in out Stage_Type;
      War  : War_Define_Type
      );

   function Switch_To_War (
      this : in out Stage_Type;
      War  : War_Define_Type
      ) return Camp_Type;

   procedure Add_Control (
      this    : in out Stage_Type;
      Control : Controls.Control_Access
      );

   procedure Remove_Control (
      this    : in out Stage_Type;
      Control : Controls.Control_Access
      );

   function Get_Fog (this : in out Stage_Type) return Controls.Fog_Access;

   function Sprite_layer (this : Stage_Type) return Map_Layer_Type;

   -- 加载地图
   procedure Load_Map (this : in out Stage_Type) is null;

   -- 加载英雄
   procedure Load_Heroes (this : in out Stage_Type) is null;

   -- 主角
   function Myself (
      this : in out Stage_Type
      ) return access Roles.Role_Type'Class;

   procedure Say (
      this     : in out Stage_Type; 
      Message  : String; 
      Linefeed : String := "*";
      Icon     : Uint16_t := 0
      );

   function Confirm (
      this : in out Stage_Type;
      Message : String
      ) return Boolean;

   function Select_Team_Member (
      this            : in out Stage_Type;
      Skill           : Skill_Type := s_Attack;
      Min_Skill_Value : Int16_t := -1
      ) return Uint16_t;


   function Select_Team_Member (
      this       : in out Stage_Type;
      Attr_Name  : UTF8_String;
      Attr_Value : not null access function (Who : Hero_Type) return UTF8_String 
      ) return Uint16_t;

   procedure Select_War_Team (
      this : in out Stage_Type;
      War  : in out War_Define_Type
      );

   procedure Shopping (this : in out Stage_Type) is null;

   procedure Iterate_Heroes (
      this : in out Stage_Type;
      Break : not null access function (Who : Roles.Role_Pointer) return Boolean
      );

   function Find_Hero_At (
      this : Stage_Type;
      Where : Point_Type
      ) return Roles.Role_Pointer;

   procedure Play_Story_Movie (
      this : in out Stage_Type; 
      Movie : Story_Movie_Type 
      );

   procedure Play_Story_Movies (
      this : in out Stage_Type; 
      Movies : Story_Movie_Array_Type
      );

   procedure Use_Equip_Item (this : in out Stage_Type; Item : in out Item_Type);

   procedure Use_Drug_Item (this : in out Stage_Type; Item : in out Item_Type);

   procedure Use_Study_Book (this : in out Stage_Type; Book : in out Item_Type);

   procedure Use_Story_Item (this : in out Stage_Type; Item : in out Item_Type) is null;

   procedure Use_Throw_Item (this : in out Stage_Type; Item : in out Item_Type) is null;


   procedure On_Passby (this : in out Stage_Type) is null;

   overriding
   procedure On_Key_Down (
      this          : in out Stage_Type;
      Message       : SDL.Events.Keyboard_Event_t;
      Cancel_Bubble : in out Boolean
      );

   overriding
   procedure On_Key_Up (
      this          : in out Stage_Type;
      Message       : SDL.Events.Keyboard_Event_t;
      Cancel_Bubble : in out Boolean
      );

   overriding
   procedure On_Mouse_Down (
      this : in out Stage_Type;
      Message : SDL.Events.Mouse_Button_Event_t;
      Cancel_bubble : in out Boolean
      );

   overriding
   procedure On_Mouse_Up (
      this : in out Stage_Type;
      Message : SDL.Events.Mouse_Button_Event_t;
      Cancel_bubble : in out Boolean
      );


   overriding
   procedure On_Timer (
      this          : in out Stage_Type;
      Timer         : Hash_Type;
      Count         : Natural;
      Cancel_Bubble : in out Boolean
      );

   overriding
   procedure Run (this : in out Stage_Type);

   overriding
   procedure Initialize (this : in out Stage_Type);

   overriding
   procedure Finalize (this : in out Stage_Type);

   procedure Popup_Menu (this : in out Stage_Type'Class);

   package Stage_Lists is 
      new Doubly_Linked_Lists(Legend.Stages.Stage_Pointer);

   Stage_Stack : Stage_Lists.List;

   function Current_Stage return Stage_Pointer;

   procedure Close_All_Stage; 

private

   -- TODO 
   -- 仙剑的场景使用的是另一实现方式:
   -- 即由美术画好一个整体的场景,
   -- 再在地图上进行道路标注
   -- 每个格子由底部及顶部两层构成, 标注时记下每层是否障碍高度即可
   -- 绘制人物时, 根据人物大小, 将所在区域按地图格大小切成小块
   -- 对每个小块计算其与人物的遮掩关系, 再重绘各小块
   -- 计算方式为, 对每一格, 检查是否有障碍物经过，
   -- 不透明部分用该格遮住人物, 反之将人物在该格的贴图绘制到原格子上
   --
   -- 
   --

   package Uint16_Sets is new Ordered_Sets(Uint16_t);


   type Building_Type is record
      West_North : Point_Type; -- 西北坐标(景深比较用)
      East_South : Point_Type; -- 东南坐标(建筑位置)
      Top_Left   : Point_Type; -- 视觉坐标
   end record;

   -- 需排序的景物
   type Scenery_Type is record
      Layer      : Map_Layer_Type;  -- 所在的层
      West_North : Point_Type;      -- 西北位置
      Location   : Point_Type;      -- 坐标
      Picture_Id : Uint16_t;
   end record;

   package Scenery_Lists is new Doubly_Linked_Lists (Scenery_Type);

   type Movie_Grid_Type is record
      Where : Point_Type;
      Layer : Map_Layer_Type;     
      Movie : Movie_Type;
   end record;

   function Is_Same_Location (
      Grid  : Movie_Grid_Type; 
      Layer : Map_Layer_Type;
      Where : Point_Type
      ) return Boolean 
      with Inline;

   package Movie_Grid_Lists is new Doubly_Linked_Lists(Movie_Grid_Type);

   type Stage_Type is new Interactives.Interactive_Type with record
      Scene        : access Scene_Type := null;
      Hero         : access Legend.Roles.Role_Type'Class := null;
      Map_Size     : Point_Type;  -- 地图大小(单位为地图格点)
      View_Size    : Point_Type;  -- 视觉地图大小(单位为视觉格点) = 地图格点 x 2
      Grid_Size    : Point_Type;  -- 视觉地图每格画布大小(单位为像素) (18 X 9)
      Origin       : Point_Type;  -- 地图原点(西北角)在视觉地图上的位置
      Sight_View   : Region_Type; -- 玩家可见区域
      Layers       : Layer_Array; -- 图层
      Visibles     : Layer_Visibility;
      Sceneries    : Scenery_Lists.List; -- 需景深排序的景物列表, 绘图时动态生成
      Pictures     : Pictures_Pointer; -- 当前画布所用的图片资源
      Heroes       : Legend.Roles.Roles_Access; -- 人物列表
      Scenes       : Uint16_Sets.Set;  -- 子场景集合
--      Menubar      : Menus.Menubar_Pointer;
      Movies       : Movie_Grid_Lists.List; -- 动画列表
      Hit_Surface  : Boolean := False; -- 是否将Surface作为悬挂层
      Cur_Story_Id : Uint16_t;  -- 当前剧情
      Components   : Controls.Control_Lists.List; -- 前景组件
      Fog          : aliased Controls.Fog_Type;
   end record;

   procedure Draw_Grid (
      this  : in out Stage_Type;
      Layer : Map_Layer_Type;
      Where : Point_Type
      );

   procedure Iterate_Layer (
      this : in out Stage_Type;
      Layer : Layer_Access;
      Corners : View_Corners_Type;
      On_Iterate_Grid : not null access procedure (Where : Point_Type)
      );

   procedure Paint_Background (
      this : in out Stage_Type;
      Corners : View_Corners_Type
      );

   procedure Paint_Surface (
      this : in out Stage_Type;
      Corners : View_Corners_Type
      );

   procedure Paint_Buildings (
      this : in out Stage_Type;
      Corners : View_Corners_Type
      );

   procedure Paint_Stories (
      this : in out Stage_Type;
      Corners : View_Corners_Type
      );

   procedure Paint_Sprites (this : in out Stage_Type);

   procedure Paint_Sceneries (this : in out Stage_Type);

   procedure Paint_Components (this : in out Stage_Type);

   -- 主菜单将要弹出前被调用
   -- 重载可实现动态菜单项效果
   procedure On_Popup_Menu (
      this : in out Stage_Type;
      Menubar : in out Menus.Menubar_Pointer
      ) is null;

   -- 响应主菜单的命令
   procedure On_Command (
      this : in out Stage_Type;
      Command : Integer
      ) is null;

   procedure On_Stage_Menu_Cure (this : in out Stage_Type);

   procedure On_Stage_Menu_Detoxify (this : in out Stage_Type);

   procedure On_Stage_Menu_State (this : in out Stage_Type);

   procedure Dispose is
      new Ada.Unchecked_Deallocation(Stage_Class, Stage_Pointer);

   Water_Obstacle : Boolean := True;

   -- 行边界类型
   type Line_Bound_Type is record
      West, East : SDL.Int16_t;
   end record;

end Legend.Stages;
